var L = window.log = function(a) {
    try {
        L.store(arguments);
        if (window.console !== undefined) {
            console.log.apply(console, arguments);
        } else if (jQuery) {
            L.record.apply(this, arguments);
        }
    } catch(e) {
        L.error = e;
        try {
            alert('There was a log error: '+L.stringify(e,0));
        } catch(e2) {
            alert('There was a log error.');
        }
    }
    return a;
};
L.version = "0.1";
L.record = function() {
    var msg = '';
    try {
        for (var i=0,m=arguments.length; i<m; i++) {
            msg += L.stringify(arguments[i], 0);
            if (i+1<m) msg += ' ';
        }
    } catch(e) {
        try {
            msg += L.stringify(e, 0);
        } catch(f) {
            msg += e;
        }
    } finally {
        L.write(msg);
    }
};
L.write = function(msg) {
    if (!L.$console) {
        L.$console = $('#log_console');
        if (L.$console.size() == 0) {
            jQuery('body').append('<div id="log_console"><b>Log:</b></div>');
            L.$console = $('#log_console');
        }
        jQuery('#log_console a.inspect').live("click", function(e) {
            var self = jQuery(this), i = self.attr('href'),
                find = 'a[name=log'+i+']', found = jQuery(find);
            e.preventDefault();
            if (found.size() == 0) {
                L.write('<a name="log'+i+'"><i>Inspecting:</i></a><br>'+
                        L.stringify(LSO.store[i], 0, "all"));
                found = jQuery(find);
            }
            var p = found.position();
            jQuery('body').animate({scrollTop: p.top, scrollLeft:p.left}, 100);
        });
    }
    L.$console.append('<div class="log">'+msg+'</div>');
};
L.store = function(args) {
    if (L.records === undefined) L.records = [];
    L.records.push(args);
};


var toStr = Object.prototype.toString,
    isArray = function(o) { return toStr.call(o) === "[object Array]"; },
    isFunction = function(o) { return toStr.call(o) === "[object Function]"; },
    hasProps = function(o) { for (i in o) if (i != 'prototype') return true; },
    LS = L.stringify = function(arg, depth, opt) {
        var type = LS.getType(arg)
        return LS['_'+type](arg, depth, type, opt);
    };
LS.getType = function(arg) {
    if (arg === null) return 'null';
    if (arg === undefined) return 'undefined';
    if (arg === window) return 'window';
    if (arg === document) return 'document';
    var to = typeof arg;
    switch (to) {
        case "string":
        case "number":
        case "boolean":
            return to;
        default:
            if (arg.preventDefault) return 'event';
            if (arg.nodeName) return 'node';
            if (arg.jquery) return 'jQuery';
            if (isArray(arg)) return 'array';
            if (isFunction(arg)) return 'function';
            return 'object';
    }
};
LS._null = LS._undefined = function() { return ''; };
LS._number = LS._boolean = function(arg) { return arg.toString(); };
LS._window = LS._document = function(a,d,type) { return type; };
LS._string = function(arg) {
    return arg.replace(/\</g,'&lt;');
};
LS._jQuery = function($s) {
    var sel = $s.selector, ctx = $s.context;
    if (ctx && ctx.nodeName) ctx = LS._node(ctx);
    if (ctx == "#document") ctx = null;
    return "$("+(sel && ctx ? sel+','+ctx : sel+ctx)+")";
};
LS._node = function(node) {
    var out = node.nodeName.toLowerCase();
    if ('id' in node && node.id) {
        out += '#'+node.id;
    } else {
        //TODO: append class(es)
    }
    return out;
};
LS._array = function(arr, depth) {
    var out = '[';
    for (var i=0,m=arr.length; i<m; i++) {
        out += L.stringify(arr[i], depth + 1);
        if (i+1<m) out += ',';
    }
    return out + ']';
};
LS._function = function(fn, depth, type, nolink) {
    var out = "function()";
    if (hasProps(fn)) {
        out += LS._object(fn, depth, type, nolink);
    }
    return out;
};
var LSO = LS._object = LS._event = function(obj, depth, type, nolink) {
    if (!hasProps(obj)) return '{}';
    var start = '', out = '', end = '', cut = false,
        all = (nolink == "all"), br = all ? '<br>' : ', ';
    if (nolink) {
        if (!all) {
            start = '{';
            end = '}';
        } else if (type == "function") {
            start = ' with properties:<br>';
        }
    } else {
        if (!LSO.store) LSO.store = [];
        var i = LSO.store.length
        LSO.store[i] = obj;
        start = '<a href="'+i+'" class="inspect">{';
        end = '}</a>';
    }
    if (depth == 0) nolink = !nolink;
    var p=0, key;
    for (key in obj) {
        if (key != 'prototype') {
            if (all || p < 3 - depth) {
                try {
                    out += key+'='+L.stringify(obj[key], depth+1, nolink);
                } catch (e) {
                    out += key+'=@protected';
                }
                out += br;
                p++;
            } else {
                cut = true;
                break;
            }
        }
    }
    out = out.substring(0, out.length-br.length);
    return start + out + (cut ? ',...' : '') + end;
};
if (jQuery) {
    (function ($) {
        $.fn.log = function() {
            L(this, arguments);
            return this;
        };
        var $doc = $(document);

        var watch = function(type) {
            if (L.watch) {
                var c = L.watch.length;
                for (var i=0; i < c; i++) {
                    if (type.indexOf(L.watch[i]) === 0) {
                        if (!L.watching) {
                            L.watching = [type];
                        } else {
                            L.watching.push(type);
                        }
                        return true;
                    }
                }
            }
            return false;
        };

        var watching = function(type) {
            if (L.watching) {
                var c = L.watching.length;
                for (var i=0; i < c; i++) {
                    if (type == L.watching[i]) {
                        return true;
                    }
                }
            }
            return false;
        };

        // keep watched event history for examination in firebug
        L.watched = {};

        // override bind to watch for interesting events
        var _bind = $.fn.bind;
        $.fn.bind = function(type, data, fn) {
            if (!watching(type) && watch(type)) {
                _bind.call($doc, type, function(e) {
                    L.watched[e.timeStamp+':'+e.type] = e;
                    log(arguments);
                });
            }
            return _bind.call(this, type, data, fn);
        };

        // be sure to log ajaxErrors
        $doc.ajaxError(function(event, xhr, opts, error) {
            // ignore handled errors
            if (opts.handled) return;
            log(opts.type, opts.url, ' -> ', 'error', xhr.status, xhr);
            // beware mysterious json errors...
            if (opts.dataType == 'json') {
                try {
                    var data = eval(xhr.responseText);
                    log(data);
                } catch (e) {
                    log(e);
                }
            }
        });

        // provide default logging for JSON-REST
        if ($.rest && window.console === undefined) {
            $.extend($.rest.options, {
                before: function(dataOrXHR, status, opts) {
                    if (!opts) opts = {};
                    log(opts.type, opts.url, ' -> ', status, dataOrXHR);
                }
            });
        }

    })(jQuery);
}
